/***************************************************************************
 *   Copyright (C) 2008 by Alejandro Rothar *
 ***************************************************************************/

#include <netinet/in.h>
#include "upload.h"

int port; /*Puerto de escucha*/
extern TipoConfiguracion Config; /*estructura de configuracion*/
char nombreModulo[LARGO_NOMBRE_MODULO];
char IDServer[16];

int main(int argc, char *argv[])
{
   struct sockaddr_in local;
   struct sockaddr_in remoto;
   int sock_escucha; /*Descriptor de server*/
   int sock_cliente; /*Descriptor del cliente*/
   int address_size; /*Se usa para saber el tamanio de sockaddr_in*/
   int pid_child; /*Se usa para saber el pid del hijo*/
   int pid_child_gnutella;
   int conexion=1;
   char buf[16384];
   
   /*********************/
   /*Para las listas*/
   /********************/
   /*TipoPingPong ping;*/
   /*TipoNodo *nodoEncontrado;*/
   
   /*TipoLista *listaPings;
   TipoLista *listaPongs;*/
   /*TipoPingLista pingListaAux;*/
   /*TipoPingLista *pingListaEncontrado;*/
   /*int cantidadElementos;*/
   /********************/
   /********************/

   /*int v=0;*/
   
   if((pid_child_gnutella=fork())==0){
   	sleep(10);
   	
   	Log("Iniciando Upload.",LOGINFO);
	/*************************************************/
	/*CAMBIO EL NOMBRE DEL PROCESO                   */	
	/*************************************************/
	strcpy(argv[0],"Upload");
	strcpy(nombreModulo,argv[0]);
	sprintf(nombreModulo, "Upload[%d]", getpid());
	Log("Renombrando completado.",LOGINFO);
	
	/*************************************************/
	/*CARGA LA CONFIGURACION DESDE EL ARCHIVO DE CONF*/	
	/*************************************************/
	CargarConfiguracion("configuracion");
	port = atoi(Config.puertoLocal);
	Log("La carga de la configuracion se ha completado correctamente.",LOGINFO);
	/*************************************************/
	/* CREA EL SOCKET PARA ESCUCHAR                  */
	/*************************************************/
	sock_escucha = socket(AF_INET, SOCK_STREAM, 0);
	if (sock_escucha == -1){
		Log("Error creado el socket.",LOGERROR);
		exit(1);
	}
	Log("Se ha creado correctamente el socket.",LOGINFO);
	/*************************************************/
	/*Configura la estructura local                  */
	/*************************************************/
	bzero((char *)&local, sizeof(local));
	local.sin_family = AF_INET;
	local.sin_addr.s_addr = INADDR_ANY;
	local.sin_port = htons(port);
	Log("Se ha completado la configuracion del socket.",LOGINFO);
	/***************************************************/
	/*Asocia el sock de escucha con la estructura local */
	/***************************************************/
	if (bind(sock_escucha, (struct sockaddr *)&local, sizeof(local)) == -1){
		perror("llamada para unir");
		exit(1);
	}
	Log("Se ha asociado el socket de escucha correctamente.",LOGINFO);
	
	if (listen(sock_escucha,20) == -1){
		Log("Error en la funcion listen. El programa se cerrara.",LOGERROR);
		exit(1);
	}
	Log("Funcion listen ha sido tratada.",LOGINFO);
	
	printf("\nNombre del proceso: %s\n", "Upload");
	printf("Direccion ip local: %s\n", Config.ipLocal);
	printf("El puerto de escucha: %d\n", port);
	
	Log("El proceso Upload esta aceptando conexiones.",LOGINFO);
	/*sleep(2);
	system("clear");
	V(v);*/
	/*Este signal lo que hace es ignorar las llamadas de los hijos cuando terminan, de esta forma,*/
	/*el hijo puede terminar bien y no convertirse en un zombie.                                  */
	signal(SIGCHLD, SIG_IGN); 
	
	while(1){
		
		/*Se queda aceptando a los clientes, este va a devolver el descriptor del nuevo cliente.*/
		address_size=sizeof(struct sockaddr_in);
		
		sock_cliente =	accept(sock_escucha, (struct sockaddr *)&remoto, (unsigned int *)&address_size);
		Log("Se ha aceptado una conexion de peticion de archivo.",LOGINFO);
		if(sock_cliente == -1){
			Log("Error al aceptar una conexion de peticion de archivo.",LOGERROR);
			
		}
		else{
			pid_child=fork();
			
			if(pid_child==0){
				Log("El servidor Upload esta lista para mandar la respuesta.",LOGINFO);
				envioRespuesta(sock_cliente);
				
				_exit(0);
			}
			else{
				if(pid_child>0){
					close(sock_cliente);
					/*No se hace nada con el padre, las señales*/
					/*se mandan directamente al SO para que los*/
					/*hijos mueran.                            */
				}
				else
					Log("Hubo un error al crear hijo.",LOGERROR);
			}
		}
	} /*Fin del while*/
	wait(NULL);
   }
   else{
   	if(pid_child_gnutella>0){
   		int leng=0;
   		fd_set descriptoresLectura;
		int socketCliente[100];
		int numeroClientes;
		char optimizador[1024];
		int i, maximo;
		ListaQuery *listaQuery=NULL;
		ListaPing *listaPing=NULL;
		/*ListaPong *listaPong=NULL;*/
		ListaVecino *listaVecino=NULL;
		ListaGnu *listaGnu=NULL;
		TipoHeaderTP buffer04;
		TipoPingPong tpp_buffer;
		struct timeval tiempoSelect;
		int opcionSelect;
		int islas=0, chequeador=0;
		
   		Log("Iniciando Gnutella", LOGINFO);
   		sleep(1);
   		
   		
   		/*************************************************/
		/*CAMBIO EL NOMBRE DEL PROCESO                   */
		/*************************************************/
		/*printf("Renombrando... ");*/
		strcpy(argv[0],"Gnutella_SRV");
		strcpy(nombreModulo,argv[0]);
		sprintf(nombreModulo, "Gnutella_SRV[%d]", getppid());

		Log("Renombrando... OK!", LOGINFO);
		/*************************************************/
		/*CARGA LA CONFIGURACION DESDE EL ARCHIVO DE CONF*/	
		/*************************************************/
		/*printf("\r\rCargando archivo de configuracion... ");*/
		CargarConfiguracion("configuracion");
		port = atoi(Config.puertoGnutella);
		
		/*puts("OK ");*/
		Log("Archivo de Configuracion cargado... OK!", LOGINFO);
		/*************************************************/
		/* CREA EL SOCKET PARA ESCUCHAR                  */
		/*************************************************/
		/*printf("Creando el socket para escuchar... ");*/
		sock_escucha = socket(AF_INET, SOCK_STREAM, 0);
		if (sock_escucha == -1){
			Log("Error creando el socket", LOGERROR);
			exit(1);
		}
		Log("Se ha creado el socket... OK!", LOGINFO);
		/*************************************************/
		/*Configura la estructura local                  */
		/*************************************************/
		/*printf("Configurando socket... ");*/
		bzero((char *)&local, sizeof(local));
		local.sin_family = AF_INET;
		local.sin_addr.s_addr = INADDR_ANY;
		local.sin_port = htons(port);
		/*puts("OK ");*/
		Log("Configuracion del socket... OK!", LOGINFO);
		/*FileLog(LOGINFO, "Configurando el socket... OK ", nombreModulo);*/
		/***************************************************/
		/*Asocia el sock de escucha con la estructura local */
		/***************************************************/
		/*printf("Asociando el sock de escucha... ");*/
		while(bind(sock_escucha, (struct sockaddr *)&local, sizeof(local)) == -1){
			local.sin_port=htons(++port);
			printf("\a");
			Log("Se ha incrementado el puerto", LOGINFO);
		}
		/*puts("OK ");
		FileLog(LOGINFO, "Asociando el sock de escucha... OK ", nombreModulo);*/
		Log("Se asocio el sock de escucha... OK!", LOGINFO);
		
		/*printf("Funcion listen... ");*/
		if (listen(sock_escucha,20) == -1){
			Log("Error en la funcion listen.", LOGERROR);
			exit(1);
		}
		/*puts("OK ");
		FileLog(LOGINFO, "Ejecutando funcion listen... OK ", nombreModulo);*/
		Log("Funcion listen... OK!", LOGINFO);
		/*Intento de conexion con los nodos*/
		if(func_comunicacionPingPong(&socketCliente[0], &listaPing, &listaVecino, &listaGnu, 0)){
			Log("No hay nodos disponibles", LOGINFO);
		}
		else{
			numeroClientes++;
			Log("Conexion con nodo... OK!", LOGINFO);
		}
		
		printf("\nNombre del proceso: %s\n", "Gnutella");
		printf("Direccion ip local: %s\n", Config.ipLocal);
		printf("El puerto de escucha: %d\n", port);
		
		/*Este signal lo que hace es ignorar las llamadas de los hijos cuando terminan, de esta forma,*/
		/*el hijo puede terminar bien y no convertirse en un zombie.                                  */
		signal(SIGCHLD, SIG_IGN); 
		/*Se queda aceptando a los clientes, este va a devolver el descriptor del nuevo cliente.*/
		strcpy(optimizador, "find ");
		strcat(optimizador, Config.rutaCompartida);
		strcat(optimizador, " -iname ···· > optimizador");
		
		system(optimizador);
		system("rm optimizador");
		/*printf("\n\nEl servidor GNUTELLA esta aceptando conexiones...\n\n");
		FileLog(LOGINFO, "El servidor GNUTELLA está linto para aceptar conexiones... ", nombreModulo);*/
		Log("El servidor GNUTELLA esta aceptando conexiones", LOGINFO);	
		while (1){
			/* Se eliminan todos los clientes que hayan cerrado la conexión */
			compactaClaves (socketCliente, &numeroClientes);
			if(islas&&(listaGnu!=NULL)&&listaVecino==NULL){
				listaPing=NULL;
				/*listaPong=NULL;*/
				listaVecino=NULL;
				if(func_comunicacionPingPong(&socketCliente[numeroClientes], &listaPing, &listaVecino, &listaGnu, 1)){
					Log("No se pudo conectar a ningun nodo.", LOGINFO);
				}
				else{
					numeroClientes++;
					compactaClaves (socketCliente, &numeroClientes);
					Log("Se reconecto a una red Gnutella.", LOGINFO);
				}
			}
			
			FD_ZERO (&descriptoresLectura);
			FD_SET (sock_escucha, &descriptoresLectura);
			
			tiempoSelect.tv_sec=atoi(Config.timeSelect);
			tiempoSelect.tv_usec=0;
			
			for (i=0; i<numeroClientes; i++)
				FD_SET (socketCliente[i], &descriptoresLectura);
	
			maximo = dameMaximo (socketCliente, numeroClientes);
			
			if (maximo < sock_escucha)
				maximo = sock_escucha;
			
			
			islas=0;
			chequeador=0;
			leng=0;
			opcionSelect=select (maximo + 1, &descriptoresLectura, NULL, NULL, &tiempoSelect);
			if(opcionSelect){
				for (i=0; i<numeroClientes; i++){
					if (FD_ISSET (socketCliente[i], &descriptoresLectura)){
						int  apuntador1=0, apuntador2=0;;
						int vectorDesconectados[10]={};
						func_envioRespuestaGNUTELLA(socketCliente[i], &port, nombreModulo, &listaQuery, &listaPing, &listaVecino, &listaGnu, &leng, &chequeador, vectorDesconectados);
						if(leng<=-1 || chequeador<=-1){
							conexion=1;
							if(leng==-1){
								if(func_eliminarVecino(&listaVecino, (int)socketCliente[i])==-1)
									Log("Un Cliente cerro la conexion", LOGINFO);
								else{
									Log("Un vecino cerro conexion", LOGINFO);
									
								}
								func_eliminarPing(&listaPing, socketCliente[i]);
								socketCliente[i] = -1;
								islas=1;
								
							}
							else{
								if(chequeador==-1){
									Log("Un vecino cerro conexion", LOGINFO);
									while(vectorDesconectados[apuntador1]>0){
										while(socketCliente[apuntador2]!=vectorDesconectados[apuntador1])
											apuntador2++;
										if(apuntador2<10){
											func_eliminarPing(&listaPing, socketCliente[i]);
											socketCliente[apuntador2]=-1;
										}
										apuntador1++;
										
									}
									islas=1;
								}
								else{
									/*i=0;
									while(socketCliente[i]>0 && i<10)
										i++;
									if(func_comunicacionPingPong(&socketCliente[i], &listaPing, &listaVecino, &listaGnu, 1)){
										numeroClientes++;
										printf("Conexion con el nodo... OK!\n");
									}*/
									islas=1;
								}
							}
						}
					}
				}
				if((islas==0)&&(chequeador==0)){
					if (FD_ISSET (sock_escucha, &descriptoresLectura)){
						address_size=sizeof(struct sockaddr_in);
						socketCliente[numeroClientes] =	accept(sock_escucha, (struct sockaddr *)&remoto,(unsigned int *) &address_size);
						if(socketCliente[numeroClientes] == -1){
							Log("Error al aceptar un nuevo cliente !!!", LOGERROR);
						}
						else{
							if((recv(socketCliente[numeroClientes],(TipoHeaderTP *)&buffer04,sizeof(TipoHeaderTP),0)) <= 0){
								Log("Error recibiendo heander de conexion", LOGERROR);
								
							}
							if(buffer04.ucTipo==HANDSHAKE){
								if(recv(socketCliente[numeroClientes],buf,16384,0) == -1){
									Log("Error recibiendo informacion de HandShake... ERROR", LOGERROR);
								}
								else{
									if((strcmp(buf, GNUTELLA_SRV_CONNECT)) == 0){
										/*buffer04.ucTipo=HANDSHAKE;*/
										if(send(socketCliente[numeroClientes],(TipoHeaderTP*)&buffer04,sizeof(TipoHeaderTP),0)==-1){
											Log("Erro enviando header de confirmacion de conexion.", LOGERROR);
										}
										else{
											if(send(socketCliente[numeroClientes],GNUTELLA_SRV_OK,strlen(GNUTELLA_SRV_OK),0)==-1){
												Log("Error enviando mensaje de confirmacion de conexion.", LOGERROR);
											}
											else{
												numeroClientes++;
												Log("Estableciendo conexion con Servidor... OK", LOGINFO);
											}
										}
										
									}
								}
							}
							else{
								if(buffer04.ucTipo==PING){
										
										if(recv(socketCliente[numeroClientes], (TipoPingPong*)&tpp_buffer, sizeof(TipoPingPong),0)==-1){
											Log("Error recibiendo cuerpo del Ping-Pong", LOGERROR);
										}
										else{
											buffer04.ucTipo=PONG;
											if(send(socketCliente[numeroClientes],(TipoHeaderTP*)&buffer04,sizeof(TipoHeaderTP),0)==-1){
												Log("Error enviando header del ping-pong Gnutella", LOGERROR);
											}
											else{
												if(send(socketCliente[numeroClientes],&buf[sizeof(TipoHeaderTP)], sizeof(TipoPingPong),0)==-1){
													Log("Error enviando cuerpo del ping-pong Gnutella", LOGERROR);
												}
												else{
													Log("Estableciendo conexion mediante Ping-Pong... OK", LOGINFO);
												}
												
											}
											
											
										}
								}
								else{
									
									if(send(socketCliente[numeroClientes],GNUTELLA_SRV_ERROR,strlen(GNUTELLA_SRV_ERROR),0)==-1){
										Log("Error enviando mensaje de error... ERROR", LOGERROR);
									}
									else
										Log("Error en la informacion para aceptar conexion, enviando mensaje de error... OK", LOGERROR);
								}
							}
						}
					}
				}
			}
			else{
				func_eliminarQuery(&listaQuery);
			}
		}
		_exit(0);
   	}
   	else{
   		Log("¡¡¡Error creando SERVER GNUTELLA!!!", LOGERROR);
   	}
   	_exit(0);
   }
   return 0;
}

